package bg.btanev.eightyfour.domain.person;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.lang.Validate;

import bg.btanev.eightyfour.domain.Target;
import bg.btanev.eightyfour.domain.entitybase.DomainEntity;
import bg.btanev.eightyfour.domain.experience.EducationExperience;
import bg.btanev.eightyfour.domain.experience.WorkExperience;
import bg.btanev.eightyfour.domain.geo.GeoLocation;
import bg.btanev.eightyfour.domain.profile.Profile;

/**
 *
 * @author B.Tanev
 *
 */
public class Person extends DomainEntity<Long> {
	/**
	 *
	 */
	private static final long serialVersionUID = -2049493521259011049L;

	private PersonName personName; // component
	private ContactInfo contactInfo; // component
	private GeoLocation geoLocation; // component

	private SortedSet<WorkExperience> workExperiences = new TreeSet<WorkExperience>();
	private SortedSet<EducationExperience> educationExperiences = new TreeSet<EducationExperience>();

	/**
	 * a reference to the related user {@link Profile}s
	 */
	private Set<Person> relatedPersons = new HashSet<Person>(); // many-to-many

	/**
	 * A reference to the {@link Person}'s {@link Target} profile.
	 */
	private Target target; // one-to-one

	protected Person() {
		super();
	}

	public Person(PersonName personName, ContactInfo contactInfo) {
		this(null, personName, contactInfo, null, null, null, null);
	}

	public Person(PersonName personName, ContactInfo contactInfo, GeoLocation location) {
		this(null, personName, contactInfo, location, null, null, null);
	}

	public Person(Long id, PersonName personName, ContactInfo contactInfo, GeoLocation location,
			Collection<Person> relatedPersons, Collection<WorkExperience> workExperiences,
			Collection<EducationExperience> educationExperiences) {

		super(id);

		setPersonName(personName);
		setContactInfo(contactInfo);

		if (location != null) {
			this.updateGeoLocation(location);
		}
		if (relatedPersons != null) {
			this.addRelatedPersons(relatedPersons);
		}
		if (workExperiences != null) {
			this.addWorkExperience(workExperiences);
		}
		if (educationExperiences != null) {
			this.addEducationExperience(educationExperiences);
		}
	}

	public void setPersonName(PersonName personName) {
		Validate.notNull(personName);
		this.personName = personName;
	}

	public void setContactInfo(ContactInfo contactInfo) {
		Validate.notNull(contactInfo);
		this.contactInfo = contactInfo;
	}

	public PersonName getPersonName() {
		return personName;
	}

	public ContactInfo getContactInfo() {
		return contactInfo;
	}

	public Target getTarget() {
		return target;
	}

	public GeoLocation getGeoLocation() {
		return geoLocation;
	}

	public void updateGeoLocation(GeoLocation location) {
		Validate.notNull(location);

		this.geoLocation = location;
	}

	public boolean addRelatedPersons(Person person) {
		Validate.notNull(person);

		return this.relatedPersons.add(person) & person.relatedPersons.add(this);
	}

	/**
	 *
	 * @param persons
	 *            a {@link Collection} of related {@link Person}s to add.
	 * @return <code>true</code> if the related profiles {@link Collection} has been changed.
	 */
	public boolean addRelatedPersons(Collection<Person> persons) {
		Validate.notNull(persons);

		boolean result = false;
		for (Person person : persons) {
			result = result | person.addRelatedPersons(this);
		}

		return result;
	}

	public boolean removeRelatedPerson(Person person) {
		Validate.notNull(person);

		return this.relatedPersons.remove(person) & person.relatedPersons.remove(this);
	}

	public Set<Person> getRelatedPersons() {
		return Collections.unmodifiableSet(relatedPersons);
	}

	public boolean addWorkExperience(WorkExperience workExperience) {
		Validate.notNull(workExperience);

		if (!(workExperience.getPerson().getContactInfo().equals(this.getContactInfo()))) {
			throw new IllegalArgumentException("work experience must be for the current person");
		}

		return this.workExperiences.add(workExperience);
	}

	public boolean addWorkExperience(Collection<WorkExperience> workExperience) {
		Validate.notNull(workExperience);

		boolean result = false;
		for (WorkExperience work : workExperience) {
			result = result | addWorkExperience(work);
		}

		return result;
	}

	public boolean removeWorkExperience(WorkExperience workExperience) {
		Validate.notNull(workExperience);

		return this.workExperiences.remove(workExperience);
	}

	public SortedSet<WorkExperience> getWorkExperiences() {
		return Collections.unmodifiableSortedSet(workExperiences);
	}

	public boolean addEducationExperience(EducationExperience eduExperience) {
		Validate.notNull(eduExperience);

		if (!(eduExperience.getPerson().getContactInfo().equals(this.getContactInfo()))) {
			throw new IllegalArgumentException("education experience must be for the current person");
		}

		return this.educationExperiences.add(eduExperience);
	}

	public boolean addEducationExperience(Collection<EducationExperience> eduExperience) {
		Validate.notNull(eduExperience);

		boolean result = false;
		for (EducationExperience edu : eduExperience) {
			result = result | addEducationExperience(edu);
		}

		return result;
	}

	public boolean removeEducationExperience(EducationExperience eduExperience) {
		Validate.notNull(eduExperience);

		return this.educationExperiences.remove(eduExperience);
	}

	public SortedSet<EducationExperience> getEducationExperiences() {
		return Collections.unmodifiableSortedSet(educationExperiences);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((contactInfo == null) ? 0 : contactInfo.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (contactInfo == null) {
			if (other.contactInfo != null)
				return false;
		} else if (!contactInfo.equals(other.contactInfo))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Person [contactInfo=" + contactInfo + ", geoLocation=" + geoLocation + ", personName=" + personName
				+ ", toString()=" + super.toString() + "]";
	}
}
